package game.Engine;

import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Vector;

import org.feiteira.network.copy.SeriClient;
import org.feiteira.network.copy.SeriServer;

import com.googlecode.cqengine.CQEngine;
import com.googlecode.cqengine.IndexedCollection;
import com.googlecode.cqengine.attribute.Attribute;
import com.googlecode.cqengine.index.hash.HashIndex;
import com.googlecode.cqengine.index.navigable.NavigableIndex;
import com.googlecode.cqengine.query.Query;
import com.googlecode.cqengine.resultset.ResultSet;

import game.MainController;
import game.Engine.Entity.Alien;
import game.Engine.Entity.AlienSpawn;
import game.Engine.Entity.Creature;
import game.Engine.Entity.Entity;
import game.Engine.Entity.Magic;
import game.Engine.Entity.Mouse;
import game.Engine.Entity.Player;
import game.Engine.Entity.PowerUp;
import game.Engine.Entity.Tile;
import game.Engine.Map.Map;
import game.Engine.Server.ClientHandler;
import game.Input.GameAction;
import game.Input.GameActionWrapper;
import game.display.Images;
import static com.googlecode.cqengine.query.QueryFactory.*;

public class GameEngine implements Runnable {
   // Variables
   public ArrayList<Player> players;
   public Mouse mouse;
   public Map currentMap;
   private State state;

   public State getState() {
      return state;
   }

   private boolean running;
   public IndexedCollection<Entity> entities;

   public volatile boolean paused;

   public static enum State {
      SINGLE_PLAYER, MULTIPLAYER_HOST, MULTIPLAYER_CLIENT
   }

   public Vector<Queue<GameActionWrapper>> buffers;
   private Queue<Entity> toAdd;

   private int currentAlienCount;
   public static boolean progress;

   public GameEngine(Map map, State state) {
      entities = CQEngine.newInstance();
      entities.addIndex(NavigableIndex.onAttribute(Entity.ENTITY_X));
      entities.addIndex(NavigableIndex.onAttribute(Entity.ENTITY_Y));
      entities.addIndex(NavigableIndex.onAttribute(Entity.ENTITY_Z));
      entities.addIndex(HashIndex.onAttribute(Entity.Entity_class));
      entities.addIndex(HashIndex.onAttribute(Entity.isPowerup));
      mouse = new Mouse(0, 0, Images.CURSOR.image());
      entities.add(mouse);
      currentMap = map;
      players = new ArrayList<Player>();
      buffers = new Vector<Queue<GameActionWrapper>>();
      clientbuffer = new ArrayList<GameActionWrapper>();
      toAdd = new LinkedList<Entity>();
      paused = false;
      this.state = state;
      loadEntitiesFromMap();
   }

   private void loadEntitiesFromMap() {
      for (Entity[] p : currentMap.entities) {
         for (Entity e : p) {
            if (e != null) {
               entities.add(e);
            }
            if (e instanceof Player) {
               players.add((Player) e);
               buffers.add(new LinkedList<GameActionWrapper>());
            }
            if (e instanceof Alien) {
               currentAlienCount++;
            }
         }
      }
   }

   public int getCurrentAlienCount() {
      return currentAlienCount;
   }

   public void addAlienCount() {
      currentAlienCount++;
   }

   public Query<Entity> all = has(Entity.ENTITY_Z);
   private ArrayList<GameActionWrapper> clientbuffer;

   public Query<Entity> getBelow(Entity e) {
      return and(equal(Entity.Entity_class, Tile.class),
            between(Entity.ENTITY_X, e.getX() - 1, e.getX() + 1),
            between(Entity.ENTITY_Y, e.getY() + .5, e.getY() + 1.5));
   }

   public Query<Entity> getPowerUpsAround(Entity e) {
      return and(equal(Entity.isPowerup, true),
            between(Entity.ENTITY_X, e.getX() - 1, e.getX() + 1),
            between(Entity.ENTITY_Y, e.getY() - 1, e.getY() + 1));
   }

   // Methods
   private void updateEntities(long elapsedTime) {
      Query<Entity> creatures = or(equal(Entity.Entity_class, Creature.class),
            equal(Entity.Entity_class, Player.class),
            equal(Entity.Entity_class, Alien.class));
      for (Entity e : entities.retrieve(creatures)) {
         boolean nofall = nofall(e);
         if (!nofall) {
            ((Creature) e).fall();
         } else {
            ((Creature) e).setOnGround(true);
         }
      }
      for (Entity e : entities.retrieve(all)) {
         e.update(elapsedTime);
      }
      // for(Entity e: entities)
      for (Iterator<Entity> i = entities.iterator(); i.hasNext();) {
         Entity e = i.next();
         if (e.getState() == Entity.State.DEAD) {
            i.remove();
            if (e instanceof Alien) {
               currentAlienCount--;
               if (e.getHitBy() instanceof Player)
                  ((Player) e.getHitBy()).addPoints(((Alien) e).getPoints());
            }
         }
      }
   }

   public boolean nofall(Entity e) {
      int underX = (int) e.getX();
      int underY = (int) e.getY() + 1;
      int underRightX = (int) Math.ceil(e.getX());
      if ((underY < 0) || (underY > getMaxY())) {
         return false;
      }
      if (underX < 0) {
         if (underRightX < 0) {
            return false;
         } else {
            return currentMap.entities[(int) Math.ceil(e.getX())][(int) e
                  .getY() + 1] instanceof Tile;
         }
      }
      if (underX >= getMaxX()) {
         return false;
      } else {
         if (underRightX >= getMaxX()) {
            return (currentMap.entities[(int) e.getX()][(int) e.getY() + 1] instanceof Tile);
         } else {
            return ((currentMap.entities[(int) e.getX()][(int) e.getY() + 1] instanceof Tile) || (currentMap.entities[(int) Math
                  .ceil(e.getX())][(int) e.getY() + 1] instanceof Tile));
         }
      }
   }

   public ResultSet<Entity> runQuery(Query<Entity> q) {
      return entities.retrieve(q);

   }

   public ResultSet<Entity> runQuery(Query<Entity> q, Attribute orderby) {
      return entities.retrieve(q, queryOptions(orderBy(orderby)));

   }

   public Query<Entity> getNear(Entity e, double x) {
      double min = Math.min((int) e.getX() - 1, x);
      double max = Math.max((int) e.getX() + 1.5, x + 1);
      return and(equal(Entity.Entity_class, Tile.class),
            between(Entity.ENTITY_X, min, max));
   }

   public Query<Entity> getwithin(Rectangle2D.Double r) {
      return or(
            between(Entity.ENTITY_X, r.x / 64.0 - 2, (r.x + r.width) / 64.0 + 2),
            between(Entity.ENTITY_Y, r.y / 64.0 - 2,
                  (r.y + r.height) / 64.0 + 2));
   }

   private void executeBufferedActions() {
      while (!buffers.get(MainController.getID()).isEmpty()) {
         executeAction(buffers.get(MainController.getID()).poll());
      }
   }

   private void executeAction(GameActionWrapper action) {
      if(action != null)
      switch (action.getGa()) {
      case PLAYER_UP:
         if (players.get(action.getSource()).isOnGround())
            players.get(action.getSource()).jump(false);
         else
            players.get(action.getSource()).jump(true);
         break;
      case PLAYER_DOWN:
         players.get(action.getSource()).slide();
         break;
      case PLAYER_LEFT:
         players.get(action.getSource()).setAcceleratingLeft(true);
         break;
      case PLAYER_RIGHT:
         players.get(action.getSource()).setAcceleratingRight(true);
         break;
      case PLAYER_LEFT_R:
         players.get(action.getSource()).setAcceleratingLeft(false);
         break;
      case PLAYER_RIGHT_R:
         players.get(action.getSource()).setAcceleratingRight(false);
         break;
      case PLAYER_DOWN_R:
         players.get(action.getSource()).standUp();
         break;
      case PLAYER_MAGIC_WHITE:
         players.get(action.getSource()).setWhite();
         break;
      case PLAYER_MAGIC_RED:
         players.get(action.getSource()).setRed();
         break;
      case PLAYER_MAGIC_GREEN:
         players.get(action.getSource()).setGreen();
         break;
      case PLAYER_MAGIC_BLUE:
         players.get(action.getSource()).setBlue();
         break;
      case PLAYER_MAGIC_YELLOW:
         players.get(action.getSource()).setYellow();
         break;
      case PLAYER_WHEEL_SCROLL_UP:
         players.get(action.getSource()).changePlayerMagicUp();
         break;
      case PLAYER_WHEEL_SCROLL_DOWN:
         players.get(action.getSource()).changePlayerMagicDown();
         break;
      case PLAYER_CAST:
         players.get(action.getSource()).cast();
         break;
      default:
         break;
      }
   }

   public void addGameAction(GameActionWrapper gameAction) {
      if (state != State.MULTIPLAYER_CLIENT) {
         buffers.get(gameAction.getSource()).add(gameAction);
      } else {
         clientbuffer.add(gameAction);
      }
   }

   public void stopGame() {
      running = false;
   }

   private SeriClient sc;

   public Player getPlayer(int i) {
      for (Entity e : entities) {
         if (e instanceof Player) {
            Player p = (Player) e;
            if (p.ID == i) {
               return p;
            }
         }
      }
      return null;
   }

   public void run() {
      long lastTime = System.nanoTime();
      long elapsedTime;
      switch (state) {
      case MULTIPLAYER_HOST:
         try {
            new SeriServer(4445).setEventListner(new ClientHandler());
         } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
         }
         break;
      case MULTIPLAYER_CLIENT:
         try {
            sc = new SeriClient("localhost", 4445);
         } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
         }
         sc.setEventListner(new game.Engine.Client.ClientEventHandler());
         new Thread(sc).start();
         try {
            sc.send("RequestID");
         } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
         }
         break;
      }
      running = true;
      while (running) {
         if (state == State.SINGLE_PLAYER) {
            elapsedTime = System.nanoTime() - lastTime;
            lastTime += elapsedTime;
            if (paused) {
               try {
                  Thread.sleep(50);
               } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
               }
            } else {
               executeBufferedActions();
               addBufferedEntities();
               MainController.soundController
                     .executeEventSoundBufferedActions();
               updateEntities(elapsedTime);
               Player p = players.get(MainController.getID());
               if (p.getState() == Entity.State.DEAD) {
                  MainController.soundController.stopMidi();
                  MainController.soundController.clearEventSoundBuffer();
                  MainController.checkForHighScore(p.getTotalPoints());
                  MainController.restart();
                  running = false;
               }
            }
         } else if (state == State.MULTIPLAYER_HOST) {

            elapsedTime = System.nanoTime() - lastTime;
            lastTime += elapsedTime;
            if (paused) {
               try {
                  Thread.sleep(50);
               } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
               }
            } else {
               executeAllBufferedActions();
               addBufferedEntities();
               MainController.soundController
                     .executeEventSoundBufferedActions();
               updateEntities(elapsedTime);
               for (Player p : players) {
                  if (p.getState() == Entity.State.DEAD) {
                     MainController.checkForHighScore(p.getTotalPoints());
                     MainController.restart();
                     running = false;
                  }
               }
            }
         } else if (state == State.MULTIPLAYER_CLIENT) {
            try {
               elapsedTime = System.nanoTime() - lastTime;
               lastTime += elapsedTime;
               sc.send("Update");
               sc.send(clientbuffer);
               for (Entity e : entities) {
                  e.updateImages(elapsedTime);
               }
               try {
                  Thread.sleep(90);
               } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
               }
            } catch (IOException e) {
               e.printStackTrace();
            }
         }
      }
   }

   private void executeAllBufferedActions() {
      for (Queue<GameActionWrapper> q : buffers) {
         while (!q.isEmpty()) {
            executeAction(q.poll());
         }
      }

   }

   private void addBufferedEntities() {
      while (!toAdd.isEmpty()) {
         entities.add(toAdd.poll());
      }
   }

   public void addEntity(Entity e) {
      toAdd.add(e);
   }

   public int getMaxY() {
      int max = 0;
      for (Entity[] p : currentMap.entities) {
         if (p.length > max) {
            max = p.length;
         }
      }
      return max;
   }

   public int getMaxX() {
      return currentMap.entities.length;
   }

   public Player findClosestPlayer(Entity e) {
      double mindist = Double.MAX_VALUE;
      Player closest = null;
      for (Player p : players) {
         double dist = Entity.distanceBetween(p, e);
         if (dist < mindist) {
            mindist = dist;
            closest = p;
         }
      }
      return closest;
   }

	public void replaceEntities(Vector newEntities) {//TODO Probably not going to work
		Entity newE;
		for(Entity e: entities){
			for(Iterator i = newEntities.iterator(); i.hasNext();){
				newE = ((Entity)i.next());
				if(e.getZ() == newE.getZ()){
					if(e instanceof Player) ((Player)e).matchWith((Player)newE);
					else if(e instanceof Alien) ((Alien)e).matchWith((Alien)newE);
					else if(e instanceof AlienSpawn) ((AlienSpawn)e).matchWith((AlienSpawn)newE);
					else if(e instanceof Magic) ((Magic)e).matchWith((Magic)newE);
					else if(e instanceof Mouse) ((Mouse)e).matchWith((Mouse)newE);
					else if(e instanceof PowerUp) ((PowerUp)e).matchWith((PowerUp)newE);
					else if(e instanceof Tile) ((Tile)e).matchWith((Tile)newE);
					i.remove();
					break;
				}
			}
		}
		
		//entities.clear();
		//entities.addAll(newEntities);
	}
}
